"""

.. module:: spaces
.. moduleauthor:: @author: 

This module defines the Spaces and SpacesContext classes. 
   
.. seealso:: modules :mod:`<module>`

*Constants*:
.. data:: constant_1
        
    constant_1 is used for ......
        
.. data:: constant_2
        
    constant_2 is used for ......
"""

# Imports
#
from Queue import Queue
import logging
import threading
import types

from datastore.resource import Resource
from datastore.uri import Uri
from runtime.boot import LoggingLevels

# Constants
#

# Classes
#

class SpacesContext():
    """This class provides context for calls to Spaces. Tasks use SpacesContext 
    to make calls to Spaces. SpacesContext delegates calls to Spaces. It also 
    provides access to a queue where callbacks are queued by Spaces to be 
    processed by the task.
    
    *Usage examples*::

        example code

    *Common Arguments for Class methods*:
        <arguments>
    
    *Audience*:
        <audience>
    
    *Constants*:
        .. data:: constant_1
        
            constant_1 is used for ......
        
        .. data:: constant_2
        
            constant_2 is used for ......
    """
    
    # Class constants
    #
    
    def __init__(self, spaces, queue):
        # Constructor
        #
        # Parameters:
        #     <param 1> :
        #     ...
        #
        
        self.logger = logging.getLogger('yarra.runtime.spaces.SpacesContext')
        self.__spaces = spaces
        
        # queue for callbacks
        #
        self.__queue = queue

    def get(self, uri, callback=None, **kwargs):
        """This function delegates to Spaces.get(). See Spaces.get() for more 
        detailed description.
        
        :param uri: Uri
        :param callback: String/Method - Either the method name (String) or a 
        reference to the method to be called back on a Task.
        :param kwargs: Dictionary - Additional keyword arguments to pass to the 
        callback method if specified. Otherwise it is ignored.
        :rtype: datastore.resource.Resource
        """
        
        return self.spaces.get(uri, self, callback, **kwargs)

    def get_wait(self, uri, callback=None, **kwargs):
        """This function delegates to Spaces.get(). See Spaces.get() for more 
        detailed description.
        
        :param uri: Uri
        :param callback: String/Method - Either the method name (String) or a 
        reference to the method to be called back on a Task.
        :param kwargs: Dictionary - Additional keyword arguments to pass to the 
        callback method if specified. Otherwise it is ignored.
        :rtype: datastore.resource.Resource
        """
        
        return self.spaces.get(uri, self, callback, wait=True, **kwargs)    
            
    def find(self, uri=None, callback=None, **kwargs):
        """This function delegates to Spaces.find(). See Spaces.find() for more 
        detailed description.
        
        :param uri: Uri
        :param callback: String/Method - Either the method name (String) or a 
        reference to the method to be called back on a Task.
        :param kwargs: Dictionary - Additional keyword arguments to pass to the 
        callback method if specified. Otherwise it is ignored.
        :rtype: List
        """
        
        return self.spaces.find(self, uri, callback, **kwargs)    

    def put(self, uri, res, callback=None, **kwargs):
        """This function delegates to Spaces.put(). See Spaces.put() for more 
        detailed description.
        
        :param uri: Uri
        :param res: Resource - resource to put in the spaces
        :param callback: String/Method - Either the method name (String) or a 
        reference to the method to be called back on a Task.
        :param kwargs: Dictionary - Additional keyword arguments to pass to the 
        callback method if specified. Otherwise it is ignored.
        :rtype: datastore.uri.Uri
        """
        
        return self.spaces.put(uri, res, self, callback, **kwargs)

    def delete(self, uri, callback=None, **kwargs):
        """This function delegates to Spaces.delete(). See Spaces.delete() for more 
        detailed description.
        
        :param uri: Uri
        :param callback: String/Method - Either the method name (String) or a 
        reference to the method to be called back on a Task.
        :param kwargs: Dictionary - Additional keyword arguments to pass to the 
        callback method if specified. Otherwise it is ignored.
        :rtype: None
        """
        
        return self.spaces.delete(uri, self, callback, **kwargs)      
    
    def remove(self, uri, callback=None, **kwargs):
        """This function delegates to Spaces.remove(). See Spaces.remove() for more 
        detailed description.
        
        :param uri: Uri
        :param callback: String/Method - Either the method name (String) or a 
        reference to the method to be called back on a Task.
        :param kwargs: Dictionary - Additional keyword arguments to pass to the 
        callback method if specified. Otherwise it is ignored.
        :rtype: datastore.resource.Resource
        """
        
        return self.spaces.remove(uri, self, callback, **kwargs)             
    
    def watch(self, methods, uri, callback, **kwargs):
        """This function delegates to Spaces.watch(). See Spaces.watch() for more 
        detailed description.
        
        :param methods: List - List containing one or more of the following - 
        'get', 'put', 'delete', 'find'.
        :param uri: Uri
        :param callback: String/Method - Either the method name (String) or a 
        reference to the method to be called back on a Task.
        :param kwargs: Dictionary - Additional keyword arguments to pass to the 
        callback method if specified. Otherwise it is ignored.
        :rtype: UUID - A watch reference that is needed for a subsequent delete_watch().
        """
        
        return self.spaces.watch(methods, uri, self, callback, **kwargs)

    def watch_all(self, methods, uri, callback, **kwargs):
        """This function delegates to Spaces.watch_all(). See Spaces.watch_all()
        for more detailed description.
        
        :param methods: List - List containing one or more of the following - 
        'get', 'put', 'delete', 'find'.
        :param uri: Uri
        :param callback: String/Method - Either the method name (String) or a 
        reference to the method to be called back on a Task.
        :param kwargs: Dictionary - Additional keyword arguments to pass to the 
        callback method if specified. Otherwise it is ignored.
        :rtype: UUID - A watch reference that is needed for a subsequent delete_watch().
        """
        
        return self.spaces.watch_all(methods, uri, self, callback, **kwargs)
    
    def delete_watch(self, watch_ref):
        """This function delegates to Spaces.delete_watch(). See Spaces.delete_watch()
        for more detailed description.
        
        :param watch_ref: a reference returned when the watch or watch_all was created.
        :rtype: None
        """
        
        self.spaces.delete_watch(watch_ref)
    
    def process(self, uri, get=None, find=None, put=None, delete=None, remove=None, **kwargs):
        """This function delegates to Spaces.process(). See Spaces.process() for
        more detailed description.
        
        :param uri: Uri
        :param get: String method name or a reference to the method to be called-back on a Task.
        :param find: String method name or a reference to the method to be called-back on a Task.
        :param put: String method name or a reference to the method to be called-back on a Task.
        :param delete: String method name or a reference to the method to be called-back on a Task.
        :param remove: String method name or a reference to the method to be called-back on a Task.
        :param kwargs: Dictionary - Additional keyword arguments to pass to the 
        callback method if specified. Otherwise it is ignored.
        :rtype: UUID - A process reference that is needed for a subsequent delete_process().
        """
        
        callback_dict = {'get':get,'find':find,'put':put,'delete':delete,'remove':remove}
        return self.spaces.process(uri, callback_dict, self, **kwargs)

    def process_all(self, uri, get=None, find=None, put=None, delete=None, remove=None, **kwargs):
        """This function delegates to Spaces.process_all(). See Spaces.process_all() for
        more detailed description.
        
        :param uri: Uri
        :param get: String method name or a reference to the method to be called-back on a Task.
        :param find: String method name or a reference to the method to be called-back on a Task.
        :param put: String method name or a reference to the method to be called-back on a Task.
        :param delete: String method name or a reference to the method to be called-back on a Task.
        :param remove: String method name or a reference to the method to be called-back on a Task.
        :param kwargs: Dictionary - Additional keyword arguments to pass to the 
        callback method if specified. Otherwise it is ignored.
        :rtype: UUID - A process reference that is needed for a subsequent delete_process().
        """
        
        callback_dict = {'get':get,'find':find,'put':put,'delete':delete,'remove':remove}
        return self.spaces.process_all(uri, callback_dict, self, **kwargs)

    def delete_process(self, process_ref):
        """This function delegates to Spaces.delete_process(). See Spaces.delete_process()
        for more detailed description.
        
        :param process_ref: a reference returned by process() or process_all(). 
        :rtype: None
        """
             
        self.spaces.delete_process(process_ref)
    
    def load_bindings(self, ns):
        """This function delegates to Spaces.load_bindings(). See Spaces.load_bindings()
        for more detailed description.
        
        :param ns: Namespace.
        :rtype: None
        """
        
        self.spaces.load_bindings(ns)
    
    def get_bindings(self):
        """This function delegates to Spaces.get_bindings(). See Spaces.get_bindings()
        for more detailed description.
        
        :rtype: None
        """
        
        return self.spaces.get_bindings()
        
    def uri(self, uri_prefix=None):
        """This function delegates to Spaces.uri(). See Spaces.uri() for more
        detailed description.
        
        :param uri_prefix: Uri - Uri prefix.
        :rtype: Uri
        """

        return self.spaces.uri(uri_prefix)

    def get_res_part(self, uri):
        """This function fetches a resource and then, if a fragment is specified,
        extracts that part from the resource.
        
        :param uri: Uri.
        :rtype: Resource or Python variable as returned by res.get_var_part()
        """

        res = self.get(uri.without_fragment())
        if res is None:
            return None
        else:
            return res.get_var_part(uri.fragment())

    def dump(self, start_uri=''):
        """This function delegates to Spaces.dump(). See Spaces.dump() for more
        detailed description.
        
        :param start_uri: String - Head Uri.
        :rtype: List - list of Uris.
        """
        
        return self.spaces.dump(self, start_uri)
    
    def return_from_callback(self, callback_return_id, return_value=None, exception=None):
        """This function delegates to Spaces.return_from_callback(). See 
        Spaces.return_from_callback() for more detailed description.
        
        :param callback_return_id: UUID - identifier for dict to retrieve callback 
        return information.
        :param return_value: ? - Any data that is to be returned to the caller.
        :rtype: None
        """
        
        self.spaces.return_from_callback(callback_return_id, return_value, exception) 
    
    def uuid(self):
        """This function delegates to Spaces.uuid(). See Spaces.uuid() for more 
        detailed description.
        
        :rtype: UUID
        """
        
        return str(self.spaces.uuid())
    
    def get_spaces(self):
        """This function returns a reference to this current SpacesContext's 
        Spaces object.
        
        :rtype: Spaces
        """
        
        return self.__spaces
    
    def get_queue(self):
        """This function returns a reference to this current SpacesContext's 
        Queue object.
        
        :rtype: Queue
        """
        
        return self.__queue
    
    spaces = property(get_spaces, None, None, None)
    queue = property(get_queue, None, None, None)
    
class Spaces(object):
    """This class defines the API used between components of Yarra. It uses a 
    data paradigm loosely modelled after REST, JavaSpaces and Linda.
    
    The Spaces class manages all data. SpacesContext delegates its methods to
    Spaces. SpacesContext adds a context (self) when it invokes Spaces methods that 
    require a queue for a callback. When a callback needs to be invoked, its 
    reference and parameters (callback_func, method, uri, res and result) are 
    placed on the SpacesContext that specified the callback. This means that a 
    callback will always be executed in the same thread that defined it.
    
    To the user of Spaces, all data appears to be stored within Spaces.
    process() and process_all, allow dynamic resources to be fetched, created
    or deleted.
    
    While common usage is to work with Resources as the value for each URI key, 
    any object may be passed.
    
    If the last character of a URI is a slash, assume that it refers to a collection.
    A collection is the same as a Web index and it will generally be possible to 
    navigate from the root to any URI.
    
    The collection uri without the trailing slash refers to the collection object.
        http:/aaa/a.b.c/ is a collection.
        http:/aaa/a.b.c is the collection index.
    
    If a callback is specified for get, put or delete, the method will return
    immediately. The callback will be invoked when a result is available.
    SpacesContext

    *Usage examples*::

        example code

    *Common Arguments for Class methods*:
        <arguments>
    
    *Audience*:
        <audience>
    
    *Constants*:
        .. data:: constant_1
        
            constant_1 is used for ......
        
        .. data:: constant_2
        
            constant_2 is used for ......
    """
    
    # Class constants
    #
    
    def __init__(self):
        # Constructor
        #
        # Parameters:
        #
        
        self.logger = logging.getLogger('yarra.runtime.spaces.Spaces')
        
        # tree of nodes which contains the resources, watches and processes 
        # associated with a uri
        self.uri_tree = UriTree()
        # maps the watch ref (key) to the watch/watch_all dictionary of a 
        # node and the method (value)
        self.watch_ref_dict = {}
        self.process_ref_dict = {}
        # callback_return_info is a dictionary that contains the info needed to 
        #    'return' data to the caller.
        # The key is a callback_return_id (unique id), the value is:
        # case: no callback:
        #    1. just a lock before the process_callback has completed.
        #    2. (lock, return_value, exception) when the process_callback has 
        #        returned to spaces.
        # case: callback specified: 
        #     - tuple (caller_spaces_queue, caller_callback, method, uri, node, 
        #        callback_return_id) (when the caller specifies a callback)
        self.callback_return_info = {}
        # key is prefix, value is the namespace
        self.ns = {}
    
    def get(self, uri, spaces_context, callback=None, wait=False, **kwargs):
        """This function fetches the Resource associated with the argument 'uri'
        
        If callback is None, return the Resource, otherwise return None and pass 
        resource to callback via the spaces_context. If the resource is not found, 
        return None. If wait is true, the method will block until the resource is 
        added to the uri, or issue a callback when the resource is added.
                
        Callback will be called with the following arguments:
            method - always 'get'
            uri - the uri passed to get
            return_value - will be the requested Resource.
            result - None or an exception class.
            spaces_context - Context which the 'method' was called from.
            **kwargs - Additional Keyword arguments passed in from the parameters of this method.
        
        :param uri: Uri
        :param spaces_context: SpacesContext - caller's spaces context.
        :param callback: String/Method - Either the method name (String) or a 
        reference to the method to be called back on a Task.
        :param wait: Boolean 
        :param kwargs: Dictionary - Additional keyword arguments to pass to the 
        callback method if specified. Otherwise it is ignored.
        :rtype: datastore.resource.Resource
        """
        
        self.logger.log(LoggingLevels.TRACE, "uri (%s), callback (%s)" % (uri, callback))
        
        node = self.uri_tree.find_node(uri)
        process_callback_info = self.find_process_callback(node, 'get')
        if process_callback_info:
            self.logger.log(LoggingLevels.TRACE, "issuing process callback")
            (return_value, exception) = self.issue_process_callback(process_callback_info, callback, spaces_context, 
                'get', uri, node, None, **kwargs)
            if callback:
                return None
        elif wait:
            self.logger.log(LoggingLevels.TRACE, "waiting")
            if callback:
                if not node.resource:
                    # if we are waiting and a callback is specified but there is currently no resource at the node, then return None
                    # immediately after we add the callback to the 'get_waits' of the node
                    node.get_waits.put((spaces_context, callback, kwargs))
                    return None
                else:
                    # if a callback is specified but there is a resource there already, then just get the resource
                    return_value = node.resource
                    exception = None
            else:
                self.logger.log(LoggingLevels.TRACE, "waiting, no callback")
                # otherwise .. just call a get_resource_wait on the node and wait for the resource
                return_value = node.get_resource_wait()
                exception = None            
        else:
            return_value = node.resource
            exception = None
            
        ########################################################################
        # This section will only NOT be reached if the caller specified a callback
        # and a call to a process associated with this uri is required.
        ########################################################################
        
        return self.handle_return(callback, 'get', 
            return_value, uri, node, exception, spaces_context, **kwargs)         
    
    def handle_return(self, callback, method, return_data, uri, node, exception, spaces_context, **kwargs):
        """This function returns the argument 'return_data' to the caller via the 
        argument 'callback' method, if supplied, or synchronise return.
        
        :param callback: String/Method - Either the method name (String) or a 
        reference to the method to be called back on a Task.
        :param method: string - the method name that was called. (i.e. 'get', 
        'put', 'delete', 'find')
        :param return_data: datastore.resource.Resource - the data to return to caller.
        :param uri: URI - uri that was called.
        :param node: runtime.spaces.Node - Node that is associated with the URI.
        :param exception: Exception - Any exception to pass back to the caller.
        :param spaces_context: runtime.spaces.SpacesContext - SpacesContext of the caller.
        :param kwargs: Dictionary - Additional keyword arguments to pass to the 
        callback method if specified. Otherwise it is ignored.
        :rtype: Anything
        """
        
        self.logger.log(LoggingLevels.TRACE, "callback (%s), method (%s), return_data (%s), uri (%s), node (%s), exception (%s)" % (callback, method, return_data, uri, node, exception))
        
        # call all the watches
        self.call_all_watches(node, method, uri, return_data, exception)
        
        # return the return_data to the caller                                
        if callback:
            spaces_context.queue.put((callback, method, uri, return_data, exception, spaces_context, kwargs))
            self.logger.log(LoggingLevels.TRACE, "********** Queue.put() args: callback (%s), method (%s), uri (%s), return_data (%s), exception (%s)" % (callback, method, uri, return_data, exception))
        else:
            # if there is a exception then raise it
            if exception:
                raise exception
            else:
                return return_data        
        
    def issue_process_callback(self, process_callback_info, caller_callback, caller_spaces_context, method, uri, node, res, **caller_kwargs):
        """This function issues the process callback and sets-up the return mechanism 
        to allow the callback to return the data.
        
        :param process_callback_info : Tuple - contains the process_callback, 
        process_spaces_context and the kwargs. The process_callback is the 
        method name (String) or a reference to the method to be called back 
        on a Process Task. The process_spaces_context is the context that was 
        used to register the process. The kwargs is the keyword arguements 
        passed to 'process' when the process was registered.
        :param caller_callback: String/Method - the method name (String) or a
        reference to the method to be called back on a Caller Task.
        :param caller_spaces_context: SpacesContext - Provides context to the 
        callback of the caller Task.
        :param method: string - the method name that was called. (i.e. 'get', 
        'put', 'delete', 'find')
        :param uri: Uri - the uri from the caller request.
        :param node: Node - the node associated with the uri.
        :param res: datastore.resource.Resource - Resource that was passed to 
        spaces. NOTE: this is only populated when 'put' is called.
        :param caller_kwargs: Dictionary - The kwargs supplied when the caller 
        called a method on spaces.
        :rtype: Anything
        """
        
        self.logger.log(LoggingLevels.TRACE, "process_callback_info (%s), caller_callback (%s), method (%s), uri (%s), node (%s), res (%s)" % (process_callback_info, caller_callback, method, uri, node, res))
        
        (process_callback, process_spaces_context, kwargs) = process_callback_info
        
        # all calls to process run in a new context instead of the default context 
        # of the task. This is because a call to a process is seen like a 'request', 
        # hence the 'request' specific data use stored in the new context. 
        new_spaces_context = SpacesContext(self, process_spaces_context.queue)
        # if caller has specified a callback
        if caller_callback:
            self.callback_return_info[new_spaces_context] = \
                (caller_spaces_context, caller_callback, method, uri, node, caller_kwargs)
            if process_callback:
                process_spaces_context.queue.put((process_callback, method, uri, res, 
                None, new_spaces_context, kwargs))
            # this handles the case if no callback is defined for a particular 
            # method on a uri that is being processed by a task. The return value 
            # will be None.
            else:
                self.return_from_callback(new_spaces_context, None)
            # always return None when callback specified
            return_value = None
            exception = None
        # if caller did not specify a callback
        else:
            if process_callback:
                # http://docs.python.org/library/threading.html#id2
                # create a lock initially unlocked.
                lock = threading.Lock()
                # change the state of the lock to 'lock' so that no one else can 
                # enter this piece of code
                lock.acquire()
                
                self.logger.log(LoggingLevels.TRACE, "lock acquire")
                
                # place lock onto the callback return info dict so that another 
                # thread (e.g. the thread that is running the the process task) 
                # can release this lock when it is done.
                self.callback_return_info[new_spaces_context] = lock
                process_spaces_context.queue.put((process_callback, method, uri, res, 
                    None, new_spaces_context, kwargs))
                # second acquire(), blocks until the lock.release() called in 
                # other thread once the lock has been released, the lock is 
                # acquired again
                lock.acquire()
                # call the release immediately, as there is no synchronized code 
                # we want to run
                
                self.logger.log(LoggingLevels.TRACE, "lock release")
                
                lock.release()
                (lock, return_value, exception) = \
                    self.callback_return_info[new_spaces_context]
            # this handles the case if no callback is defined for a particular 
            # method on a uri that is being processed by a task. The return value 
            # will be None.                
            else:
                return_value = None
                exception = None
        return (return_value, exception)
                
    def return_from_callback(self, spaces_context, return_value=None, exception=None):
        """This function returns result from callback to the caller. When task 
        callback methods need to return data to spaces as a result of it being 
        invoked, the callback method should call this method and supply the 
        spaces_context and the return value. This spaces_context is used to retrieve 
        information about the caller whose actions resulted in the callback being 
        called. The return data is then passed back to the caller (either through 
        a return or another callback).
        
        :param spaces_context: SpacesContext - identifier for dict to retrieve 
        callback return information.
        :param return_value: ? - Any data that is to be returned to the caller.
        :param exception: Exception - Any Exception that needs to be returned 
        to caller
        :rtype: None
        """
        
        self.logger.log(LoggingLevels.TRACE, "return_value (%s), exception (%s)" % (return_value, exception))
        
        try:
            callback_return_info = self.callback_return_info[spaces_context]
        except KeyError:
            # if we can't find the context to return to. just ignore it
            return
        if type(callback_return_info) is types.TupleType:
            self.logger.log(LoggingLevels.TRACE, "tuple")
            
            (caller_spaces_context, callback, method, uri, node, caller_kwargs) = callback_return_info
            caller_spaces_context.queue.put((callback, method, uri, 
                return_value, exception, caller_spaces_context, caller_kwargs))
            self.call_all_watches(node, method, uri, return_value, exception)
        else:
            self.logger.log(LoggingLevels.TRACE, "lock")
            
            lock = callback_return_info
            self.callback_return_info[spaces_context] = (lock, return_value, exception)
            lock.release()
        
    def call_all_watches(self, node, method, uri, return_value, exception):
        """This function ...
        
        :param node: runtime.spaces.Node - Node that is associated with the URI.
        :param method: string - the method name that was called. (i.e. 'get', 
        'put', 'delete', 'find')
        :param uri: URI - uri that was called.
        :param return_value: datastore.resource.Resource - the data to return to caller.
        :param exception: Exception - Any exception to pass back to the caller.
        :rtype: None
        """
        
        self.logger.log(LoggingLevels.TRACE, "node (%s), method (%s), uri (%s), return_value (%s), exception (%s)" % (node, method, uri, return_value, exception))
        
        # call the watch on the current node
        if method in node.watch:
            (watch_callback, watch_spaces_context, kwargs) = node.watch[method]
            new_spaces_context = SpacesContext(self, watch_spaces_context.queue)
            new_spaces_context.queue.put((watch_callback, method, uri, return_value, exception, new_spaces_context, kwargs))
            self.logger.log(LoggingLevels.TRACE, "********** Queue.put() args: watch_callback (%s), method (%s), uri (%s), return_value (%s), exception (%s)" % (watch_callback, method, uri, return_value, exception))
        
        # call all the watch_alls on the parent nodes
        curr_node = node
        while curr_node.parent:
            curr_node = curr_node.parent
            if method in curr_node.watch_all:
                (watch_all_callback, watch_all_spaces_context, kwargs) = curr_node.watch_all[method]
                new_spaces_context = SpacesContext(self, watch_all_spaces_context.queue)
                new_spaces_context.queue.put((watch_all_callback, method, uri, return_value, exception, new_spaces_context, kwargs))
                self.logger.log(LoggingLevels.TRACE, "********** Queue.put() args: watch_all_callback (%s), method (%s), uri (%s), return_value (%s), exception (%s)" % (watch_all_callback, method, uri, return_value, exception))
        
    def find_process_callback(self, curr_node, method):
        """This function returns the most relevant process() or process_all()
        callback for current node.
        
        The first process callback associated with the uri will be returned. First 
        check the 'process' callback for the current node. If there is not a process 
        associated with the current node, then look for the first 'process_all'  
        callback associated with the parent nodes.
        
        :param curr_node: runtime.spaces.Node - The node to find the callback for.
        :param method: string - the method name that was called. (i.e. 'get', 
        'put', 'delete', 'find')
        :rtype: Tuple
        """
        
        self.logger.log(LoggingLevels.TRACE, "curr_node (%s), method (%s)" % (curr_node, method))
        
        # if there is no process associated with current node and method, 
        # check the parent for process_all
        if not method in curr_node.process:
            if curr_node.parent:
                # find process_all callback for parent
                callback_process_info = self.find_process_all_callback(curr_node.parent, method)
            else:
                return None
        # there is a process associated with the method and uri
        else:
            # return the callback_process_info
            callback_process_info = curr_node.process[method]
        return callback_process_info
                         
    def find_process_all_callback(self, curr_node, method):
        """This function returns the most relevant process_all() callback for 
        current node.
        
        The first process_all callback associated with the uri will be returned. 
        First check the 'process_all' callback for the current node. If there is 
        not a process associated with the current node, then look for the first 
        'process_all' callback associated with the parent nodes.
                
        :param curr_node: runtime.spaces.Node - The node to find the callback for.
        :param method: string - the method name that was called. (i.e. 'get', 
        'put', 'delete', 'find')
        :rtype: Tuple
        """
        
        self.logger.log(LoggingLevels.TRACE, "curr_node (%s), method (%s)" % (curr_node, method))
        
        # if there is no process_all associated with current node and method, 
        # check the parent for process_all
        if not method in curr_node.process_all:
            if curr_node.parent:
                # find process_all callback for parent
                return self.find_process_all_callback(curr_node.parent, method)
            else:
                return None
        # there is a process_all associated with the method and uri            
        else:
            return curr_node.process_all[method]
            
    def find(self, spaces_context, uri=None, callback=None, **kwargs):
        """This function returns a list of Uri objects that match the uri prefix.
        
        Scope is limited to the next level in the uri path. 
        If uri is None then a list of the top level uri's is returned.
            http://aaa/
            http://bbb/

        Callback will be called with the following arguments:
            method - always 'find'
            uri - the uri passed to find
            return_value - will be a list of URI's.
            result - None or an exception class.
            spaces_context - Context which the 'method' was called from.
            **kwargs - Additional Keyword arguments passed in from the parameters of this method.
        
        :param spaces_context: SpacesContext - Provides context to the Caller's 
        callback.
        :param uri: Uri
        :param callback: String/Method - Either the method name (String) or a 
        references to the method to be called back on a Task.
        :param kwargs: Dictionary - Additional keyword arguments to pass to the 
        callback method if specified. Otherwise it is ignored.
        :rtype: List
        """
        
        self.logger.log(LoggingLevels.TRACE, "uri (%s), callback (%s)" % (uri, callback))
        
        node = self.uri_tree.find_node(uri)
        process_callback_info = self.find_process_callback(node, 'find')
        if process_callback_info:
            (return_value, exception) = self.issue_process_callback(process_callback_info, callback, spaces_context, 'find', uri, node, None, **kwargs)
            if callback:
                return None
        else:
            if uri:
                curr_node = self.uri_tree.find_node(uri)
            else:
                curr_node = self.uri_tree.root_node
            return_value = [child_node.uri() for child_node in curr_node.children.values()]
            exception = None
            
        ########################################################################
        # This section will only NOT be reached if the caller specified a callback
        # and a call to a process associated with this uri is required.
        ########################################################################
        
        return self.handle_return(callback, 'find', return_value, 
            uri, node, exception, spaces_context, **kwargs)
            
    def put(self, uri, res, spaces_context, callback=None, **kwargs):
        """This function saves the specified Resource using this URI as its key.
        
        If the last character of the URI is a slash, generate a UUID and append 
        it to the URI. If a process is defined for this particular uri, then don't 
        perform this default action.

        If callback is None, return the saved URI, otherwise return None and pass 
        the saved URI to callback via the spaces_context.
        
        Callback will be called with the following arguments:
            method - always 'put'
            uri - the new uri (which will be the one passed unless this is an 
                addition to a collection)
            return_value - will be the Uri of where the resource lives.
            result - None or an exception class.
            spaces_context - Context which the 'method' was called from.
            **kwargs - Additional Keyword arguments passed in from the parameters of this method.
            
            If there was a non-fatal fault but a URI was returned, then any retry
            should use the returned uri to prevent duplicate resource creation.
        
        :param uri: Uri
        :param res: Resource - resource to put in the spaces
        :param spaces_context: SpacesContext - Provides context to the Caller's 
        callback.
        :param callback: String/Method - Either the method name (String) or a 
        references to the method to be called back on a Task.
        :param kwargs: Dictionary - Additional keyword arguments to pass to the 
        callback method if specified. Otherwise it is ignored.
        :rtype: datastore.uri.Uri
        """
        
        self.logger.log(LoggingLevels.TRACE, "uri (%s), res (%s), callback (%s)" % (uri, res, callback))
        
        node = self.uri_tree.find_node(uri)
            
        process_callback_info = self.find_process_callback(node, 'put')
        if process_callback_info:
            self.logger.log(LoggingLevels.TRACE, "Processing callback info")
            (return_value, exception) = self.issue_process_callback(process_callback_info, callback, spaces_context, 'put', uri, node, res, **kwargs)
            if callback:
                return None
        else:
            self.logger.log(LoggingLevels.TRACE, "Not Processing callback info")
            new_uri = uri
            # if the uri ends with a slash then generate a unique id and append 
            # to end of uri
            if uri.is_collection():
                uuid = self.uuid();
                # get uri as string
                uri_string = str(uri) + str(uuid)
                new_uri = self.uri(uri_string)
                
                # get the parent uri of the current uri and the node to add to
                # this parent
                uri_split = new_uri.split2()
                parent_uri_string = ''.join(uri_split[:-1])
                part = uri_split[-1]
                parent_uri = self.uri(parent_uri_string)
                node = Node(part)
                node.resource = res
                # add node to parent uri
                self.uri_tree.add_node(node, parent_uri)
            else:
                node = self.uri_tree.find_node(new_uri)
                node.resource = res
            return_value = new_uri
            exception = None
            # this piece of code is to handle the callbacks for the get_wait. Since the callback is ONLY 
            # invoked when a 'put' is made to spaces, I've placed the code here as it doesnt apply to 
            # any other methods on spaces. 
            while not node.get_waits.empty():
                # pop each callback off the queue and put the callback on the queue of the spaces_context
                (get_wait_spaces_context, get_wait_callback, caller_kwargs) = node.get_waits.get()
                
                self.logger.log(LoggingLevels.TRACE, "********** Queue.put() args: callback (%s), method (put), uri (%s), res (%s), exception (%s)" % (get_wait_callback, uri, res, exception))
                
                get_wait_spaces_context.queue.put((get_wait_callback, 'put', uri, res, exception, get_wait_spaces_context, caller_kwargs))
                
        ########################################################################
        # This section will only NOT be reached if the caller specified a callback
        # and a call to a process associated with this uri is required.
        ########################################################################
        
        self.logger.log(LoggingLevels.TRACE, "Before handle_return()")
        
        return self.handle_return(callback, 'put', return_value, uri, node, exception, spaces_context, **kwargs)

    def delete(self, uri, spaces_context, callback=None, **kwargs):
        """This function deletes the resource associated with this URI in spaces.
        
        If callback is None, return the Resource that is to be deleted, otherwise 
        return None and pass the Resource to callback via the spaces_context. If 
        the Resource was not found or deleted, return None.        

        Callback will be called with the following arguments:
            method - always 'delete'
            uri - the uri passed to delete
            return_value - will be None for this case.
            result - None or an exception class.
            spaces_context - Context which the 'method' was called from.
            **kwargs - Additional Keyword arguments passed in from the parameters of this method.
        
        :param uri: Uri
        :param spaces_context: SpacesContext - Provides context to the Caller's 
        callback.
        :param callback: String/Method - Either the method name (String) or a 
        references to the method to be called back on a Task.
        :param kwargs: Dictionary - Additional keyword arguments to pass to the 
        callback method if specified. Otherwise it is ignored.
        :rtype: None
        """
        
        self.logger.log(LoggingLevels.TRACE, "uri (%s), callback (%s)" % (uri, callback))
        
        node = self.uri_tree.find_node(uri)
        process_callback_info = self.find_process_callback(node, 'delete')
        if process_callback_info:
            (return_value, exception) = self.issue_process_callback(process_callback_info, callback, spaces_context, 'delete', uri, node, None, **kwargs)
            if callback:
                return None
        else:
            node = self.uri_tree.delete_node(uri)
            return_value = None
            exception = None
            
        ########################################################################
        # This section will only NOT be reached if the caller specified a callback
        # and a call to a process associated with this uri is required.
        ########################################################################
        
        return self.handle_return(callback, 'delete', return_value, 
            uri, node, exception, spaces_context, **kwargs) 
    
    def remove(self, uri, spaces_context, callback=None, **kwargs):
        """This function returns and deletes the resource associated with this 
        URI in spaces.
        
        If callback is None, return the Resource that is to be deleted, otherwise 
        return None and pass the Resource to callback via the spaces_context. If 
        the Resource was not found or deleted, return None.        

        Callback will be called with the following arguments:
            method - always 'delete'
            uri - the uri passed to delete
            return_value - will be the Resource removed from the uri.
            result - None or an exception class.
            spaces_context - Context which the 'method' was called from.
            **kwargs - Additional Keyword arguments passed in from the parameters of this method.
        
        :param uri: Uri
        :param spaces_context: SpacesContext - Provides context to the Caller's 
        callback.
        :param callback: String/Method - Either the method name (String) or a 
        references to the method to be called back on a Task.
        :param kwargs: Dictionary - Additional keyword arguments to pass to the 
        callback method if specified. Otherwise it is ignored.
        :rtype: None
        """
        
        self.logger.log(LoggingLevels.TRACE, "uri (%s), callback (%s)" % (uri, callback))
        
        node = self.uri_tree.find_node(uri)
        process_callback_info = self.find_process_callback(node, 'remove')
        if process_callback_info:
            (return_value, exception) = self.issue_process_callback(process_callback_info, callback, spaces_context, 'remove', uri, node, None, **kwargs)
            if callback:
                return None
        else:
            # delegate to get and delete. Do not pass callback to them
            return_value = self.get(uri, spaces_context)
            self.delete(uri, spaces_context)
            exception = None
            
        ########################################################################
        # This section will only NOT be reached if the caller specified a callback
        # and a call to a process associated with this uri is required.
        ########################################################################
        
        return self.handle_return(callback, 'remove', return_value,
            uri, node, exception, spaces_context, **kwargs) 
    
    def watch(self, methods, uri, spaces_context, callback, **kwargs):
        """This function registers a watch against the Uri.
        
        The callback is called whenever the specified methods are called on the 
        uri. This method does not delay the action it is watching.
        
        A typical use of watch is to update a collection index, when a member of
        the collection is changed.
        
        There can be multiple watch and watch_all specified. Each one that matches
        the method(s) and uri will be called.
        
        Valid values for method are: 'get', 'put', 'delete', 'find', 'remove'
        
        Callback will be called with the following arguments:
            method - 'get', 'put', 'find', 'delete' or 'remove'
            uri - the uri that is being processed
            return_value - any value returned by spaces.
            result - None or an exception class.
            spaces_context - New context for each cb invoked.
            **kwargs - Additional Keyword arguments passed in from the parameters of this method.
            
        No return data is expected from the callback.
        
        :param methods: List - List contain one or more of the following - 'get',
        'put', 'delete', 'find'
        :param uri: Uri
        :param spaces_context: SpacesContext - Provides context to the Caller's 
        callback.
        :param callback: String/Method - Either the method name (String) or a 
        references to the method to be called back on a Task.
        :param kwargs: Dictionary - Additional keyword arguments to pass to the 
        callback method if specified. Otherwise it is ignored.
        :rtype: UUID
        """
        
        self.logger.log(LoggingLevels.TRACE, "methods (%s), uri (%s) callback (%s)" % (methods, uri, callback))
        
        watch_ref = str(self.uuid())
        
        self.watch_ref_dict[watch_ref] = []
        node = self.uri_tree.find_node(uri)
        node = self.uri_tree.find_non_blank_parent_node(node)
        for method in methods:
            if method in node.watch:
                raise Exception(
                    'Watch already exists for uri (%s) and method (%s) combination' 
                    % (uri, method))
            else:
                node.watch[method] = (callback, spaces_context, kwargs)
            self.watch_ref_dict[watch_ref].append((node.watch, method))
        return watch_ref

    def watch_all(self, methods, uri, spaces_context, callback, **kwargs):
        """This function registers a watch_all against the uri.
        
        The callback is called whenever the specified methods are called that
        match this uri prefix. This method does not delay the action it is watching.
        
        See watch() for additional information.
        
        uri is the uri prefix to watch. If it is None, all uri changes are detected.
        
        The callback uri is the actual uri that changed rather than the prefix 
        that was passed to watch().
            
        No return data is expected from the callback.
        
        :param methods: List - List contain one or more of the following - 'get',
        'put', 'delete', 'find'
        :param uri: Uri
        :param spaces_context: SpacesContext - Provides context to the Caller's 
        callback.
        :param callback: String/Method - Either the method name (String) or a 
        references to the method to be called back on a Task.
        :param kwargs: Dictionary - Additional keyword arguments to pass to the 
        callback method if specified. Otherwise it is ignored.
        :rtype: UUID
        """
        
        self.logger.log(LoggingLevels.TRACE, "methods (%s), uri (%s) callback (%s)" % (methods, uri, callback))
        
        watch_ref = str(self.uuid())
        
        self.watch_ref_dict[watch_ref] = []
        node = self.uri_tree.find_node(uri)
        node = self.uri_tree.find_non_blank_parent_node(node)
        for method in methods:
            if method in node.watch_all:
                raise Exception(
                    'Watch All already exists for uri and method combination')
            else:
                node.watch_all[method] = (callback, spaces_context, kwargs)
            self.watch_ref_dict[watch_ref].append((node.watch_all, method))
        
        return watch_ref
           
    def delete_watch(self, watch_ref):
        """This function deletes the specified watch() or watch_all() callback.
        
        The delete is based on the watch_ref passed in as a parameter
        
        :param watch_ref: UUID - reference to watch when saved.  
        :rtype: None
        """
        
        self.logger.log(LoggingLevels.TRACE, "watch_ref (%s)" % watch_ref)
        
        if watch_ref in self.watch_ref_dict:
            for (watch_dict, method) in self.watch_ref_dict[watch_ref]:
                del watch_dict[method]
        
    def process(self, uri, callback_dict, spaces_context, **kwargs):
        """This function registers callbacks against a uri to process all method 
        (i.e. 'get', 'put', 'delete' and 'find') on the Uri.
        
        The callback is called to process the specified methods called on the uri. 
        The task that initiated the request will not get a result until this method 
        has returned. Returns a process_ref that is required to delete the process 
        callback. An exception is raised if this particular uri already has a 
        process callback with the same method and uri. There should be a single 
        'owner' of this part of API. If more than one process or process_all 
        applies to this uri, then only the most specific is applied.

        Valid values for method are: 'get', 'put', 'delete', 'find', 'remove'.
        
        Callback will be called with the following arguments:
            method - 'get', 'put', 'find', 'delete' or 'remove'
            uri - the uri that is being processed
            return_value - any value passed on by spaces. Usually this will be 
                None, except for the case where the 
            method is 'put', in which the resource being put is passed in through 
                this parameter.
            result - None or an exception class.
            spaces_context - New context for each cb invoked.
            **kwargs - Additional Keyword arguments passed in from the parameters of this method.
            
        The callback must return:
            A Resource or None for a get.
            A Uri or None for a put.
            The resource being deleted or None for a delete.
            A list of Uri or None for a find
        
        :param uri: Uri
        :param callback_dict: Dict - key is the method (e.g 'get'). value is 
        either the method name (String) or a references to the method to be called 
        back on a Task.
        :param spaces_context: SpacesContext - Provides context to the Caller's 
        callback.
        :param kwargs: Dictionary - Additional keyword arguments to pass to the 
        callback method if specified. Otherwise it is ignored.
        :rtype: UUID
        """
        
        self.logger.log(LoggingLevels.TRACE, "uri (%s), callback_dict (%s)" % (uri, callback_dict))
    
        process_ref = str(self.uuid())
        self.process_ref_dict[process_ref] = []
                
        node = self.uri_tree.find_node(uri)
        node = self.uri_tree.find_non_blank_parent_node(node)

        for method in callback_dict.keys():
            if method in node.process:
                raise Exception(
                    'Process already exists for uri (%s) and method (%s) combination' 
                    % (uri, method))
            elif callback_dict[method]:
                node.process[method] = (callback_dict[method], spaces_context, kwargs)
            self.process_ref_dict[process_ref].append((node.process, method))
        
        return process_ref

    def process_all(self, uri, callback_dict, spaces_context, **kwargs):
        """This function registers callbacks against a uri prefix to process all 
        method (i.e. 'get', 'put', 'delete' and 'find') on the Uri.
        
        The callback is called to process the specified methods called that 
        match this uri prefix. The task that initiated the request will not 
        get a result until this method has returned.
        See process() for additional information.
        uri is the uri prefix to process. If it is None, all uri changes are processed.
        An exception is raised if this particular uri prefix already has a process 
        callback with the same method and uri prefix.
        
        :param uri: Uri
        :param callback_dict: Dict - key is the method (e.g 'get'). value is 
        either the method name (String) or a references to the method to be called 
        back on a Task.
        :param spaces_context: SpacesContext - Provides context to the Caller's 
        callback.
        :param kwargs: Dictionary - Additional keyword arguments to pass to the 
        callback method if specified. Otherwise it is ignored.
        :rtype: UUID
        """
        
        self.logger.log(LoggingLevels.TRACE, "uri (%s), callback_dict (%s)" % (uri, callback_dict))
        
        process_ref = str(self.uuid())
        self.process_ref_dict[process_ref] = []
        
        node = self.uri_tree.find_node(uri)
        node = self.uri_tree.find_non_blank_parent_node(node)
        
        for method in callback_dict.keys():
            if method in node.process_all:
                raise Exception(
                    'Process_all already exists for uri (%s) and method (%s) combination' 
                    % (uri, method))
            elif callback_dict[method]:
                node.process_all[method] = (callback_dict[method], spaces_context, kwargs)
            self.process_ref_dict[process_ref].append((node.process_all, method))
       
        return process_ref

    def delete_process(self, process_ref):
        """This function delets the specified process or process_all callback.

        The delete is based on the process_ref passed in as a parameter
        
        :param process_ref: UUID - reference to process when saved.
        :rtype: None
        """
        
        self.logger.log(LoggingLevels.TRACE, "process_ref (%s)" % process_ref)

        if process_ref in self.process_ref_dict:
            for (process_dict, method) in self.process_ref_dict[process_ref]:
                del process_dict[method]
        
    def load_bindings(self, ns):
        """This function loads bindings into spaces.

        :param ns: Namespace
        """
        
        self.logger.log(LoggingLevels.TRACE, "ns (%s)" % ns)
        
        for key in ns.keys():
            self.ns[key] = ns[key]
    
    def get_bindings(self):
        """This function returns the spaces bindings.
        """
        
        self.logger.log(LoggingLevels.TRACE, "")
        
        return self.ns
    
    def uri(self, uri_prefix=None):
        """This function returns a uri object with uri_prefix.

        :param uri_prefix: Uri - prefix of Uri
        """
        
        self.logger.log(LoggingLevels.TRACE, "uri_prefix (%s)" % uri_prefix)
        
        return Uri(uri_prefix, self.ns)

    def dump(self, spaces_context, start_uri=''):
        """This function lists all URI's in spaces, optionally starting at a 
        particular level. This is mostly to assist in development.

        :param spaces_context: SpacesContext
        :param start_uri: Uri - start level
        """
        
        uri_list = []
        self._dump_next_level(uri_list, start_uri, spaces_context)
        return uri_list
    
    def _dump_next_level(self, uri_list, next_level, spaces_context):
        """This function ...

        :param uri_list: List - list of Uris
        :param next_level: Uri 
        :param spaces_context: SpacesContext
        """
        
        uris_this_level = self.find(spaces_context, next_level)
        if uris_this_level and len(uris_this_level) > 0:
            for next_uri in uris_this_level:
                uri_list.append(next_uri)
                self._dump_next_level(uri_list, next_uri, spaces_context)
        
    def uuid(self):
        """This function generate a random uuid.
        """
        
        import uuid
        return uuid.uuid4()
        

class UriTree(object):
    """This class implements a Tree consisting of a network of nodes.
    
    The Tree class allows us to create a tree and find, add and delete nodes from 
    it. Each node in the tree represents a part of a uri. The path from a node to 
    its root node represents a uri. Each Node contains the watches, processes and 
    resource associated with a uri.
    
    NOTE: Always use the UriTree API to add, find and delete nodes in the tree. 
    The API that UriTree offers to manipulate the tree is thread-safe and we can 
    not guarantee thread safety if users try to update the tree or a node through  
    the Node API.
    
    *Usage examples*::

        example code

    *Common Arguments for Class methods*:
        <arguments>
    
    *Audience*:
        <audience>
    
    *Constants*:
        .. data:: constant_1
        
            constant_1 is used for ......
        
        .. data:: constant_2
        
            constant_2 is used for ......
    """
    
    # Class constants
    #
    
    def __init__(self):
        # Constructor
        #
        # Parameters:
        #
        
        self.__root_node = Node()
        self.__r_lock = threading.RLock()
        
    def add_node(self, node, uri):
        """This function adds the argument node to the tree and returns the node.
        
        :param node: runtime.spaces.Node - node to be added
        :rtype: runtime.spaces.Node
        """
        
        with self.__r_lock:
            child_node = self.find_node(uri)
            child_node.add_child_node(node)
            return node
    
    def find_node(self, uri):
        """This function finds and returns a node in the tree that matches the
        argument 'uri'.
        
        :param uri: Uri - Uri of child to find
        :rtype: runtime.spaces.Node
        """
        
        with self.__r_lock:        
            curr_node = self.__root_node
            if uri: 
                parts = uri.split2()
                if parts:
                    for part in parts:
                        next_node = curr_node.find_node(part)
                        if next_node:
                            curr_node = next_node
                        else:
                            curr_node = self.__create_node(curr_node, part)
            return curr_node
    
    def delete_node(self, uri):
        """This function removes the node from the tree and any of its children,
        and returns the node that was deleted.
        
        :param node: runtime.spaces.Node - node to be deleted
        :rtype: runtime.spaces.Node
        """
        
        with self.__r_lock:        
            child_node = self.find_node(uri)
            child_parent = child_node.parent
            if child_parent:
                return child_parent.delete_node(child_node)
            else:
                # can't delete a node without a parent. E.g. the root node
                return None
    
    def __create_node(self, parent_node, part):
        """This function creates a new Node, adds it as a child to the argument
        'parent_node', and return it.
        
        :param parent_node: runtime.spaces.Node - parent of new Node.
        :param part: string - Uri path part.
        :rtype: runtime.spaces.Node
        """
        
        with self.__r_lock:
            node = Node(part)
            parent_node.add_child_node(node)
            return node
    
    def find_non_blank_parent_node(self, node):
        """This function ...
        
        :param node: runtime.spaces.Node
        :rtype: runtime.spaces.Node
        """
        
        if not node.part:
            if node.parent:
                return self.find_non_blank_parent_node(node.parent)
            else:
                return None
        return node
    
    def get_root_node(self):
        """This function returns the root node of the tree.
        
        :rtype: runtime.spaces.Node
        """
        
        with self.__r_lock:
            return self.__root_node
    
    root_node = property(get_root_node, None, None, None)
    
class Node(object):
    """This class implements a node in a network of Nodes. 
    
    A network of nodes make up a tree. Each node has a reference to its parent 
    and its children. It also stores a resource associated with the node, in 
    addition to the task that have processes and watches against it.
    
    *Usage examples*::

        example code

    *Common Arguments for Class methods*:
        <arguments>
    
    *Audience*:
        <audience>
    
    *Constants*:
        .. data:: constant_1
        
            constant_1 is used for ......
        
        .. data:: constant_2
        
            constant_2 is used for ......
    """
    
    def __init__(self, part=None):
        # Constructor
        #
        # Parameters:
        #     part: Uri path part
        #
        
        # parent node
        self.__parent = None
        # child nodes
        self.__children = {}
        # each section of the uri is considered a part (e.g. part/part/part). 
        # The base uri is a part on its own.
        self.__part = part
        # queue of callbacks that have issued get_waits and are waiting for a callback
        self.get_waits = Queue()
        # dict of watchers
        self.__watch = {}
        # dict of all watchers
        self.__watch_all = {}
        # dict of processes
        self.__process = {}
        # dict of all processes
        self.__process_all = {}
        # resource associated with node
        self.__resource = None
        # lock must be held whenever the node is mutating.  All methods
        # that acquire lock must release it before returning.  lock
        # is shared between the three conditions, so acquiring and
        # releasing the conditions also acquires and releases mutex.
        self.lock = threading.Lock()
        # Notify not_empty whenever an resource is added to the node; a
        # thread waiting to get is notified then.
        self.not_empty = threading.Condition(self.lock)     
        
    def find_node(self, part):
        """This function finds a node that is a direct child of the current
        instance, that has a part that matches the argument 'part'. The child
        Node that matches the part is returned.
        
        :param part: String - part that makes up the path of the uri
        :rtype: runtime.spaces.Node
        """
        
        if part in self.children:
            return self.children[part]
        else:
            return None
    
    def delete_node(self, node):
        """This function removes the argument 'node' from its parent node,
        and returns the removed Node.
        
        :param node: runtime.spaces.Node - node to be deleted
        :rtype: runtime.spaces.Node
        """
        
        if node.part in self.children:
            del self.children[node.part]
            return node
        else:
            return None
    
    def add_child_node(self, node):
        """This function adds the argument 'node' as a child to the current 
        instance. The child node is returned.
        
        :param node: runtime.spaces.Node - node to be added
        :rtype: runtime.spaces.Node
        """
        
        node.parent = self
        self.children[node.part] = node
        return self.children[node.part]
    
    def get_parent(self):
        return self.__parent
    
    def set_parent(self, parent_node):
        self.__parent = parent_node
        
    def get_children(self):
        return self.__children
    
    def get_part(self):
        return self.__part
    
    def get_watch(self):
        return self.__watch
    
    def get_watch_all(self):
        return self.__watch_all
    
    def get_process(self):
        return self.__process
    
    def get_process_all(self):
        return self.__process_all
    
    def get_resource(self):
        return self.__resource
    
    def get_resource_wait(self):
        # acquire the lock before we do anything
        self.not_empty.acquire()        
        try:
            while not self.__resource:
                # while there is no resource for the node, wait until someone puts a resource here
                self.not_empty.wait()
            return self.__resource
        finally:
            # release the lock
            self.not_empty.release()        
        
    def set_resource(self, resource):
        self.not_empty.acquire()
        self.__resource = resource
        try:
            # when a resource is set, we should notify anyone waiting for the resource
            self.not_empty.notify()
        finally:
            self.not_empty.release()
        
    def uri(self):
        """This function returns a URI object representing the uri associated 
        with the current instance.
        
        :rtype: Uri
        """
        
        uri_path = None
        curr_node = self
        # if the parent has a part, then the parent is not a root node
        if curr_node.parent and curr_node.parent.part != None:
            # for each node go through and collect information to make up the uri
            while curr_node and curr_node.part != None:
                #  create the uri string
                if uri_path != None:
                    uri_path = curr_node.part + uri_path
                else:
                    uri_path = curr_node.part
                curr_node = curr_node.parent
        # if the parent has no part, then the parent is the root node, hence 
        # return the curr_node as the uri prefix node
        else:
            uri_path = curr_node.part
        return Uri(uri_path)
                    
    parent = property(get_parent, set_parent, None, None)
    children = property(get_children, None, None, None)
    part = property(get_part, None, None, None)
    watch = property(get_watch, None, None, None)
    watch_all = property(get_watch_all, None, None, None)
    process = property(get_process, None, None, None)
    process_all = property(get_process_all, None, None, None)
    resource = property(get_resource, set_resource, None, None)